#!/usr/bin/env node

// Initialize memory configuration early
import { MemoryManager } from './memory-init.js';
MemoryManager.initialize();

import express from 'express';
import cors from 'cors';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
  McpError,
  ErrorCode,
  JSONRPCMessage,
  JSONRPCRequest,
  JSONRPCResponse,
} from '@modelcontextprotocol/sdk/types.js';

import { ClassFinder } from './class-finder.js';
import { JavaDecompiler } from './decompiler.js';
import { LocalRepository } from './local-repository.js';
import { configManager, config } from './config.js';

/**
 * MCP HTTP Transport Server
 * Implements MCP protocol over HTTP using JSON-RPC 2.0 as per MCP specification
 */
class JavaDecompilerMCPHttpServer {
  private app: express.Application;
  private mcpServer: Server;
  private sessions = new Map<string, any>();

  // Service instances
  private classFinder: ClassFinder;
  private decompiler: JavaDecompiler;
  private localRepository: LocalRepository;

  constructor() {
    console.error(`[HTTP MCP Server] 初始化HTTP Java反编译MCP服务器...`);
    
    // Print configuration
    configManager.printConfig();
    
    this.app = express();

    // Initialize MCP Server
    this.mcpServer = new Server({
      name: 'java-decompiler-mcp-http',
      version: '1.0.0',
    });

    // Initialize services
    this.classFinder = new ClassFinder();
    this.decompiler = new JavaDecompiler();
    this.localRepository = new LocalRepository(this.classFinder);

    // Use configuration from config file
    this.localRepository.setLocalRepositoryPath(config.maven.localRepository);
    console.error(`[HTTP MCP Server] 使用配置的Maven仓库路径: ${config.maven.localRepository}`);
    
    // Initialize repository index
    console.error(`[HTTP MCP Server] 初始化仓库索引...`);
    this.localRepository.initializeIndex().catch(error => {
      console.error(`[HTTP MCP Server] 索引初始化失败: ${error}`);
    });

    this.setupMCPHandlers();
    this.setupHttpMiddleware();
    this.setupHttpRoutes();
  }

  private setupMCPHandlers() {
    // List tools handler
    this.mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'search_classes',
            description: 'Search for Java classes in local Maven repository (returns class paths only, no source code)',
            inputSchema: {
              type: 'object',
              properties: {
                keyword: { type: 'string', description: 'Keyword to search for in class names' },
                limit: { type: 'number', description: 'Maximum number of results to return (default: 50, max: 200)' },
                repositoryPath: { type: 'string', description: 'Optional: custom Maven repository path' }
              },
              required: ['keyword']
            }
          },
          {
            name: 'decompile_class',
            description: 'Decompile a specific Java class by its full class name',
            inputSchema: {
              type: 'object',
              properties: {
                fullClassName: { type: 'string', description: 'Full class name (e.g., "com.example.service.UserService")' },
                jarPath: { type: 'string', description: 'Optional: specific JAR file path if known' },
                repositoryPath: { type: 'string', description: 'Optional: custom Maven repository path' }
              },
              required: ['fullClassName']
            }
          }
        ]
      };
    });

    // Call tool handler
    this.mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
      const { name, arguments: args } = request.params;

      try {
        switch (name) {
          case 'search_classes':
            return await this.handleSearchClasses(args as {
              keyword: string;
              limit?: number;
              repositoryPath?: string;
            });
          
          case 'decompile_class':
            return await this.handleDecompileClass(args as {
              fullClassName: string;
              jarPath?: string;
              repositoryPath?: string;
            });
          
          // Legacy support
          case 'find_class_in_local_repository':
            return await this.handleLegacyFindClass(args as {
              className: string;
              repositoryPath?: string;
            });

          default:
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown tool: ${name}`
            );
        }
      } catch (error) {
        throw new McpError(
          ErrorCode.InternalError,
          `Error executing tool ${name}: ${error}`
        );
      }
    });
  }

  private setupHttpMiddleware() {
    // CORS configuration for MCP HTTP transport
    if (config.mcp.enableCors) {
      this.app.use(cors({
        origin: config.security.corsOrigins.includes('*') ? true : config.security.corsOrigins,
        credentials: true,
        methods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
        allowedHeaders: ['Content-Type', 'Accept', 'Authorization', 'Mcp-Session-Id', 'Origin']
      }));
    }

    // Parse JSON bodies
    this.app.use(express.json({ limit: '50mb' }));
    this.app.use(express.urlencoded({ extended: true }));

    // Add security headers
    this.app.use((req, res, next) => {
      res.setHeader('X-Content-Type-Options', 'nosniff');
      res.setHeader('X-Frame-Options', 'DENY');
      res.setHeader('X-XSS-Protection', '1; mode=block');
      next();
    });
  }

  private setupHttpRoutes() {
    // Main MCP HTTP endpoint
    this.app.post('/mcp', async (req, res) => {
      try {
        const jsonRpcRequest = req.body as JSONRPCRequest;
        
        // Validate JSON-RPC format
        if (!jsonRpcRequest.jsonrpc || jsonRpcRequest.jsonrpc !== '2.0') {
          return res.status(400).json({
            jsonrpc: '2.0',
            id: jsonRpcRequest.id || null,
            error: {
              code: -32600,
              message: 'Invalid Request: jsonrpc field must be "2.0"'
            }
          });
        }

        // Handle session management
        const sessionId = req.headers['mcp-session-id'] as string;
        if (sessionId && !this.sessions.has(sessionId)) {
          return res.status(400).json({
            jsonrpc: '2.0',
            id: jsonRpcRequest.id,
            error: {
              code: -32001,
              message: 'Invalid session ID'
            }
          });
        }

        // Handle initialization request
        if (jsonRpcRequest.method === 'initialize') {
          const newSessionId = this.generateSessionId();
          this.sessions.set(newSessionId, {
            createdAt: new Date(),
            lastUsed: new Date()
          });

          const initResponse: JSONRPCResponse = {
            jsonrpc: '2.0',
            id: jsonRpcRequest.id,
            result: {
              protocolVersion: config.mcp.protocolVersion,
              capabilities: {
                tools: {},
                resources: {},
                prompts: {},
                streaming: config.mcp.enableStreaming
              },
              serverInfo: {
                name: 'java-decompiler-mcp-http',
                version: '1.0.0'
              },
              sessionId: newSessionId
            }
          };

          return res.json(initResponse);
        }

        // Process request through direct handlers
        let response: any;
        
        try {
          if (jsonRpcRequest.method === 'tools/list') {
            const result = await this.handleListTools();
            response = {
              jsonrpc: '2.0',
              id: jsonRpcRequest.id,
              result
            };
          } else if (jsonRpcRequest.method === 'tools/call') {
            const result = await this.handleCallTool(jsonRpcRequest.params);
            response = {
              jsonrpc: '2.0',
              id: jsonRpcRequest.id,
              result
            };
          } else {
            throw new McpError(
              ErrorCode.MethodNotFound,
              `Unknown method: ${jsonRpcRequest.method}`
            );
          }
        } catch (error) {
          response = {
            jsonrpc: '2.0',
            id: jsonRpcRequest.id,
            error: {
              code: error instanceof McpError ? error.code : ErrorCode.InternalError,
              message: error instanceof Error ? error.message : 'Internal server error',
              data: error instanceof McpError ? error.data : undefined
            }
          };
        }
        
        // Update session if exists
        if (sessionId && this.sessions.has(sessionId)) {
          this.sessions.get(sessionId).lastUsed = new Date();
        }

        // Check if streaming is requested and enabled
        const acceptHeader = req.headers.accept || '';
        if (config.mcp.enableStreaming && acceptHeader.includes('text/event-stream')) {
          res.setHeader('Content-Type', 'text/event-stream');
          res.setHeader('Cache-Control', 'no-cache');
          res.setHeader('Connection', 'keep-alive');
          if (config.mcp.enableCors) {
            res.setHeader('Access-Control-Allow-Origin', '*');
          }
          
          // Send SSE response
          res.write(`data: ${JSON.stringify(response)}\n\n`);
          res.end();
        } else {
          res.json(response);
        }

      } catch (error) {
        console.error('MCP HTTP Server Error:', error);
        
        const errorResponse = {
          jsonrpc: '2.0',
          id: req.body?.id || null,
          error: {
            code: -32000,
            message: error instanceof Error ? error.message : 'Internal server error',
            data: error instanceof McpError ? error.data : undefined
          }
        };

        res.status(500).json(errorResponse);
      }
    });

    // Session management endpoints
    this.app.delete('/mcp/session/:sessionId', (req, res) => {
      const sessionId = req.params.sessionId;
      if (this.sessions.has(sessionId)) {
        this.sessions.delete(sessionId);
        res.json({ message: 'Session terminated' });
      } else {
        res.status(404).json({ error: 'Session not found' });
      }
    });

    // Health check endpoint
    this.app.get('/health', (req, res) => {
      res.json({
        status: 'ok',
        timestamp: new Date().toISOString(),
        protocol: 'MCP over HTTP',
        version: config.mcp.protocolVersion,
        activeSessions: this.sessions.size,
        config: {
          host: config.server.host,
          port: config.server.port,
          cors: config.mcp.enableCors,
          streaming: config.mcp.enableStreaming
        }
      });
    });

    // MCP protocol information endpoint
    this.app.get('/mcp/info', (req, res) => {
      res.json({
        name: 'Java Decompiler MCP HTTP Server',
        version: '1.0.0',
        protocol: 'MCP over HTTP',
        protocolVersion: config.mcp.protocolVersion,
        transport: 'Streamable HTTP',
        serverUrl: configManager.getServerUrl(),
        capabilities: {
          tools: true,
          resources: false,
          prompts: false,
          streaming: config.mcp.enableStreaming,
          sessionManagement: true
        },
        endpoints: {
          main: configManager.getMcpEndpoint(),
          health: configManager.getHealthEndpoint(),
          info: configManager.getInfoEndpoint(),
          sessionDelete: `${configManager.getServerUrl()}/mcp/session/:sessionId`
        },
        configuration: {
          sessionTimeout: config.mcp.sessionTimeout,
          maxConcurrentSessions: config.mcp.maxConcurrentSessions,
          corsEnabled: config.mcp.enableCors,
          corsOrigins: config.security.corsOrigins
        }
      });
    });

    // 404 handler
    this.app.use((req, res) => {
      res.status(404).json({
        jsonrpc: '2.0',
        error: {
          code: -32004,
          message: 'Endpoint not found. Use POST /mcp for MCP requests.'
        }
      });
    });
  }

  private generateSessionId(): string {
    return `mcp-session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }


  // New method: Search classes (only returns metadata, no source code)
  private async handleSearchClasses(args: { keyword: string; limit?: number; repositoryPath?: string }) {
    console.error(`[HTTP MCP Server] 收到搜索类请求: ${args.keyword}`);
    
    const limit = Math.min(args.limit || 50, 200); // Default 50, max 200
    
    try {
      if (args.repositoryPath) {
        console.error(`[HTTP MCP Server] 使用自定义仓库路径: ${args.repositoryPath}`);
        this.localRepository.setLocalRepositoryPath(args.repositoryPath);
        await this.localRepository.initializeIndex();
      }

      console.error(`[HTTP MCP Server] 搜索关键字: ${args.keyword}, 限制: ${limit}`);
      const indexEntries = await this.localRepository.findClassesByName(args.keyword);
      const limitedEntries = indexEntries.slice(0, limit);
      
      console.error(`[HTTP MCP Server] 找到 ${indexEntries.length} 个匹配，返回 ${limitedEntries.length} 个`);

      const searchResults = limitedEntries.map(entry => ({
        fullName: entry.fullName,
        className: entry.className,
        packageName: entry.packageName,
        artifact: {
          groupId: entry.artifact.groupId,
          artifactId: entry.artifact.artifactId,
          version: entry.artifact.version,
          jarPath: entry.artifact.localPath
        }
      }));

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              keyword: args.keyword,
              totalFound: indexEntries.length,
              returned: searchResults.length,
              results: searchResults
            }, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: false,
              error: `Search failed: ${error}`,
              keyword: args.keyword
            })
          }
        ]
      };
    }
  }

  // New method: Decompile a specific class
  private async handleDecompileClass(args: { fullClassName: string; jarPath?: string; repositoryPath?: string }) {
    console.error(`[HTTP MCP Server] 收到反编译请求: ${args.fullClassName}`);
    
    try {
      if (args.repositoryPath) {
        this.localRepository.setLocalRepositoryPath(args.repositoryPath);
        await this.localRepository.initializeIndex();
      }

      let classInfo;
      let classBytes;

      if (args.jarPath) {
        // If JAR path is provided, use it directly
        console.error(`[HTTP MCP Server] 使用指定的JAR路径: ${args.jarPath}`);
        classInfo = {
          className: args.fullClassName.split('.').pop() || args.fullClassName,
          fullName: args.fullClassName,
          packageName: args.fullClassName.includes('.') ? 
            args.fullClassName.substring(0, args.fullClassName.lastIndexOf('.')) : '',
          jarPath: args.jarPath
        };
        classBytes = await this.classFinder.getClassSource(classInfo);
      } else {
        // Search for the class by exact full name
        console.error(`[HTTP MCP Server] 搜索精确匹配的类: ${args.fullClassName}`);
        const indexEntries = await this.localRepository.findClassesByFullName(args.fullClassName);
        
        if (indexEntries.length === 0) {
          return {
            content: [
              {
                type: 'text',
                text: JSON.stringify({
                  success: false,
                  error: `Class not found: ${args.fullClassName}. Please use 'search_classes' tool first to find the exact class name.`,
                  fullClassName: args.fullClassName
                })
              }
            ]
          };
        }

        // Use the first match
        const indexEntry = indexEntries[0];
        classInfo = {
          className: indexEntry.className,
          fullName: indexEntry.fullName,
          packageName: indexEntry.packageName,
          jarPath: indexEntry.artifact.localPath
        };
        classBytes = await this.classFinder.getClassSource(classInfo);
      }

      if (!classBytes) {
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify({
                success: false,
                error: "Failed to extract class bytecode from JAR",
                fullClassName: args.fullClassName
              })
            }
          ]
        };
      }

      const decompileResult = await this.decompiler.decompileClass(classBytes, args.fullClassName);
      
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: decompileResult.success,
              fullClassName: args.fullClassName,
              jarPath: classInfo.jarPath,
              source: decompileResult.success ? decompileResult.source : undefined,
              error: !decompileResult.success ? decompileResult.error : undefined
            }, null, 2)
          }
        ]
      };
    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: false,
              error: `Decompilation failed: ${error}`,
              fullClassName: args.fullClassName
            })
          }
        ]
      };
    }
  }

  // Legacy method with deprecation warning
  private async handleLegacyFindClass(args: { className: string; repositoryPath?: string }) {
    console.error(`[HTTP MCP Server] ⚠️ Legacy API called: find_class_in_local_repository`);
    
    // Check if it looks like a full class name
    if (!args.className.includes('.')) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              warning: "This tool is deprecated. Please use the new two-step process:",
              steps: [
                "1. Use 'search_classes' to find classes by keyword",
                "2. Use 'decompile_class' with the full class name to get source code"
              ],
              hint: `For example, search for '${args.className}' first, then decompile specific class like 'com.example.${args.className}'`
            }, null, 2)
          }
        ]
      };
    }

    // If it's a full class name, redirect to decompile_class
    return this.handleDecompileClass({
      fullClassName: args.className,
      repositoryPath: args.repositoryPath
    });
  }

  private async handleFindClassInLocalRepository(args: { className: string; repositoryPath?: string }) {
    console.error(`[HTTP MCP Server] 收到在本地仓库中查找类请求: ${args.className}`);
    
    try {
      // Set custom repository path if provided
      if (args.repositoryPath) {
        console.error(`[HTTP MCP Server] 使用自定义仓库路径: ${args.repositoryPath}`);
        this.localRepository.setLocalRepositoryPath(args.repositoryPath);
        // Re-initialize index for new repository path
        await this.localRepository.initializeIndex();
      }

      // Use index-based search for much better performance
      console.error(`[HTTP MCP Server] 使用索引快速搜索类: ${args.className}`);
      const indexEntries = await this.localRepository.findClassesByName(args.className);
      console.error(`[HTTP MCP Server] 索引搜索完成，找到 ${indexEntries.length} 个匹配的类`);

      const foundClasses: any[] = [];

      // Process each found class and decompile it
      for (const indexEntry of indexEntries) {
        try {
          console.error(`[HTTP MCP Server] 反编译类: ${indexEntry.fullName}`);
          
          // Create a temporary ClassInfo object for decompilation
          const classInfo = {
            className: indexEntry.className,
            fullName: indexEntry.fullName,
            packageName: indexEntry.packageName,
            jarPath: indexEntry.artifact.localPath
          };
          
          // Get class bytes and decompile
          const classBytes = await this.classFinder.getClassSource(classInfo);
          if (classBytes) {
            const decompileResult = await this.decompiler.decompileClass(classBytes, indexEntry.fullName);
            
            foundClasses.push({
              className: indexEntry.className,
              fullName: indexEntry.fullName,
              packageName: indexEntry.packageName,
              artifact: {
                groupId: indexEntry.artifact.groupId,
                artifactId: indexEntry.artifact.artifactId,
                version: indexEntry.artifact.version,
                localPath: indexEntry.artifact.localPath
              },
              decompiled: decompileResult.success,
              source: decompileResult.success ? decompileResult.source : decompileResult.error
            });
            
            console.error(`[HTTP MCP Server] 类 ${indexEntry.fullName} 反编译${decompileResult.success ? '成功' : '失败'}`);
          } else {
            console.error(`[HTTP MCP Server] 无法获取类字节码: ${indexEntry.fullName}`);
            foundClasses.push({
              className: indexEntry.className,
              fullName: indexEntry.fullName,
              packageName: indexEntry.packageName,
              artifact: {
                groupId: indexEntry.artifact.groupId,
                artifactId: indexEntry.artifact.artifactId,
                version: indexEntry.artifact.version,
                localPath: indexEntry.artifact.localPath
              },
              decompiled: false,
              source: "Failed to extract class bytecode from JAR"
            });
          }
        } catch (error) {
          console.error(`[HTTP MCP Server] 处理类失败 ${indexEntry.fullName}: ${error}`);
          foundClasses.push({
            className: indexEntry.className,
            fullName: indexEntry.fullName,
            packageName: indexEntry.packageName,
            artifact: {
              groupId: indexEntry.artifact.groupId,
              artifactId: indexEntry.artifact.artifactId,
              version: indexEntry.artifact.version,
              localPath: indexEntry.artifact.localPath
            },
            decompiled: false,
            source: `Decompilation failed: ${error}`
          });
        }
      }
      
      console.error(`[HTTP MCP Server] 索引搜索完成，成功处理 ${foundClasses.length} 个类`);

      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              className: args.className,
              repositoryPath: args.repositoryPath || this.localRepository.getLocalRepositoryPath(),
              totalArtifactsScanned: await this.localRepository.getIndexedArtifactCount(),
              totalClassesIndexed: await this.localRepository.getIndexedClassCount(),
              matchesFound: foundClasses.length,
              indexReady: this.localRepository.isIndexReady(),
              classes: foundClasses
            }, null, 2)
          }
        ]
      };

    } catch (error) {
      return {
        content: [
          {
            type: 'text',
            text: JSON.stringify({
              success: false,
              error: `Failed to find class in local repository: ${error}`,
              className: args.className,
              indexReady: this.localRepository.isIndexReady()
            })
          }
        ]
      };
    }
  }

  private async handleListTools() {
    return {
      tools: [
        {
          name: 'search_classes',
          description: 'Search for Java classes in local Maven repository (returns class paths only, no source code)',
          inputSchema: {
            type: 'object',
            properties: {
              keyword: { type: 'string', description: 'Keyword to search for in class names' },
              limit: { type: 'number', description: 'Maximum number of results to return (default: 50, max: 200)' },
              repositoryPath: { type: 'string', description: 'Optional: custom Maven repository path' }
            },
            required: ['keyword']
          }
        },
        {
          name: 'decompile_class',
          description: 'Decompile a specific Java class by its full class name',
          inputSchema: {
            type: 'object',
            properties: {
              fullClassName: { type: 'string', description: 'Full class name (e.g., "com.example.service.UserService")' },
              jarPath: { type: 'string', description: 'Optional: specific JAR file path if known' },
              repositoryPath: { type: 'string', description: 'Optional: custom Maven repository path' }
            },
            required: ['fullClassName']
          }
        }
      ]
    };
  }

  private async handleCallTool(params: any) {
    const { name, arguments: args } = params;

    try {
      switch (name) {
        case 'search_classes':
          return await this.handleSearchClasses(args as {
            keyword: string;
            limit?: number;
            repositoryPath?: string;
          });
        
        case 'decompile_class':
          return await this.handleDecompileClass(args as {
            fullClassName: string;
            jarPath?: string;
            repositoryPath?: string;
          });
        
        // Legacy support
        case 'find_class_in_local_repository':
          return await this.handleLegacyFindClass(args as {
            className: string;
            repositoryPath?: string;
          });

        default:
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Unknown tool: ${name}`
          );
      }
    } catch (error) {
      throw new McpError(
        ErrorCode.InternalError,
        `Error executing tool ${name}: ${error}`
      );
    }
  }

  public start(): Promise<void> {
    return new Promise((resolve) => {
      this.app.listen(config.server.port, config.server.host, () => {
        const baseUrl = configManager.getServerUrl();
        console.log(`🚀 Java Decompiler MCP HTTP Server running on ${baseUrl}`);
        console.log(`📡 MCP Protocol Version: ${config.mcp.protocolVersion}`);
        console.log(`🔧 Transport: Streamable HTTP`);
        console.log(`📊 Health Check: ${configManager.getHealthEndpoint()}`);
        console.log(`ℹ️  Server Info: ${configManager.getInfoEndpoint()}`);
        console.log(`🎯 Main Endpoint: ${configManager.getMcpEndpoint()}`);
        resolve();
      });
    });

    // Cleanup sessions periodically
    setInterval(() => {
      const now = new Date();
      for (const [sessionId, session] of this.sessions.entries()) {
        const timeDiff = now.getTime() - session.lastUsed.getTime();
        if (timeDiff > config.mcp.sessionTimeout) {
          this.sessions.delete(sessionId);
          console.log(`Cleaned up expired session: ${sessionId}`);
        }
      }
    }, 5 * 60 * 1000); // Check every 5 minutes
  }
}

// Start server if run directly
if (import.meta.url === `file://${process.argv[1]}`) {
  const server = new JavaDecompilerMCPHttpServer();
  
  server.start().catch(console.error);
  
  // Graceful shutdown
  process.on('SIGINT', () => {
    console.log('\n🛑 Shutting down MCP HTTP server...');
    process.exit(0);
  });
}

export { JavaDecompilerMCPHttpServer };